#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stdbool.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>


#define NUM 1024
#define SIZE 64
int retcode = 0;
char env[NUM];  //环境变量具有全局性，数据应保存在全局范围内

char* getUsername()
{
    char* user = getenv("USER");
    if(user==NULL)
        return "none";
    return user;
}

char* getHostname()
{
    char* host = getenv("HOSTNAME");
    if(host==NULL)
        return "none";
    return host;
}

char* getCwd()
{
    char* cwd = getenv("PWD");
    if(cwd==NULL)
        return "none";
    return cwd;
}

int getUserCommand(char* com,int num)
{
    printf("[%s@%s %s]# ",getUsername(),getHostname(),getCwd());
    char* r = fgets(com,num,stdin);
    if(r == NULL)
        return 1;
    //注意，fgets会读取到后面用户输入的'\n'，因此需要处理
    com[strlen(com)-1] = '\0';
    return strlen(com);
}


void commandSplit(char* com,char* argv[])
{
    int argc = 0;
    char* split = " ";
    argv[argc++] = strtok(com,split);
    while(argv[argc++] = strtok(NULL,split));
#ifdef DEBUG
    for(int i = 0;i<argc;i++)
    {
        printf("%d : %s\n",i,argv[i]);
    }
#endif
}

int execute(char* argv[])
{
    //以fork创建子进程的方式进行进程替换
    pid_t pid = fork();
    if(pid == 0) //子进程
    {
        execvp(argv[0],argv);  //使用命令时并不会指定其路径，而是让系统到环境变量设置的路径下找
        //执行到这里说明替换失败
        exit(1);
    }
    else //父进程
    {
        int status;
        pid_t rid = waitpid(pid,&status,0);
        if(rid > 0)
        {
            if(WIFEXITED(status))
                retcode = WEXITSTATUS(status);
        }
    }
    return 0;
}

void cd(const char* path)
{
    chdir(path);  //chdir不会修改环境变量中的PWD，需要手动修改
    char tmp[NUM];
    getcwd(tmp,sizeof(tmp));  //获取当前的工作目录
    sprintf(env,"PWD=%s",tmp); //转换为环境变量的格式存到全局数组中
    putenv(env);  //更新环境变量PWD
}
bool doBuildin(char*argv[])
{
    if(strcmp(argv[0],"cd") == 0)
    {
        char* path = NULL; 
        if(argv[1] != NULL)
            path = argv[1];
        else 
            path = ".";
        cd(path);
        return true;
    }
    else if(strcmp(argv[0],"echo") == 0)
    {
        if(*argv[1] == '$')
        {
            char* str = argv[1] + 1;
            if(strcmp(str,"?") == 0)
            {
                printf("%d\n",retcode);
            }
            else 
            {
                char* ret = getenv(str);
                if(ret)
                    printf("%s\n",getenv(str));
                else 
                    printf("%s\n",argv[1]);
            }
        }
        else
        {
            printf("%s\n",argv[1]);
        }
        return true;
    }
    else
    {
        return false;
    }

}
int main()
{
    char command[NUM];
    char* argv[SIZE];  //命令行参数是一张表，每个元素是一个char*指针，指向一个参数
    while(1) //命令行是循环输入的
    {

        //1、获取用户输入
        int n = getUserCommand(command,sizeof(command));
        if(n <= 0) 
            continue;
        //2、分割字符串
        commandSplit(command,argv);
        //3、先判断并执行内键命令
        n = doBuildin(argv);
        if(n)
            continue;
        //4、执行普通命令
        execute(argv);
    }
    return 0;
}
